﻿#region GNU General Public License
/*
-----------------------------------------------------------------------------
This source file is part of jscc
    (Javascript with Classes)
For the latest info, see http://code.google.com/p/jscc

Copyright (c) 2011 Tapani Valkonen

jscc is free software: you can redistribute it and/or modify it under 
the terms of the GNU General Public License as published by the 
Free Software Foundation, either version 3 of the License, or (at your option)
any later version. 
 
jscc is distributed in the hope that it will be useful, 
but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 
for more details.

You should have received a copy of the GNU General Public License 
along with jscc. If not, see http://www.gnu.org/licenses/.
-----------------------------------------------------------------------------
*/
#endregion

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace jscc
{
    public enum KeywordType
    {
        Class,

        Public,
        Private,
        Static,

        Var,
        None
    }

    public class Scanner
    {
        Dictionary<string, KeywordType> keywords = new Dictionary<string, KeywordType>();

        public Scanner()
        {
            keywords["class"] = KeywordType.Class;

            keywords["public"] = KeywordType.Public;
            keywords["private"] = KeywordType.Private;
            keywords["static"] = KeywordType.Static;
            keywords["var"] = KeywordType.Var;
        }

        public TokenStream Scan(string filename, string input)
        {
            TokenStream stream = new TokenStream();

            int storeIndex = 0;
            int line = 1;

            for (int i = 0; i < input.Length; i++)
            {
                char c = input[i];
                SourceLocation loc = new SourceLocation(filename, input, line, i - storeIndex, i);

                if (Char.IsWhiteSpace(c))
                {
                    if (c == '\n')
                    {
                        line++; 
                        storeIndex = i;
                    }

                    continue;
                }
                else if (Char.IsLetter(c) || c == '_')
                {
                    Token t = ScanIdentifier(input, ref i, loc);

                    stream.Add(ScanKeywords(t));

                    continue;
                }
                else if (Char.IsDigit(c))
                {
                    stream.Add(ScanDigit(input, ref i, loc)); continue;
                }
                else if (c == '/' && i + 1 < input.Length)
                {
                    if (ScanComment(input, ref i, ref line)) continue;
                }

                switch (c)
                {
                    case '+': stream.Add(new Token(TokenType.Plus, loc)); continue;
                    case '-': stream.Add(new Token(TokenType.Minus, loc)); continue;
                    case '*': stream.Add(new Token(TokenType.Multiply, loc)); continue;
                    case '/': stream.Add(new Token(TokenType.Divide, loc)); continue;

                    case '{': stream.Add(new Token(TokenType.BraOpen, loc)); continue;
                    case '}': stream.Add(new Token(TokenType.BraClose, loc)); continue;
                    case '(': stream.Add(new Token(TokenType.ParOpen ,loc)); continue;
                    case ')': stream.Add(new Token(TokenType.ParClose ,loc)); continue;
                    case '[': stream.Add(new Token(TokenType.SbraOpen ,loc)); continue;
                    case ']': stream.Add(new Token(TokenType.SbraClose ,loc)); continue;

                    case '.': stream.Add(new Token(TokenType.Dot, loc)); continue;
                    case ',': stream.Add(new Token(TokenType.Period, loc)); continue;
                    case ':': stream.Add(new Token(TokenType.Colon, loc)); continue;
                    case ';': stream.Add(new Token(TokenType.Semi, loc)); continue;
                    case '=': stream.Add(new Token(TokenType.Assign, loc)); continue;

                    case '!': stream.Add(new Token(TokenType.ExcMark, loc)); continue;
                    case '<': stream.Add(new Token(TokenType.Greater, loc)); continue;
                    case '>': stream.Add(new Token(TokenType.Less, loc)); continue;

                    case '\'': stream.Add(ScanString('\'', input, ref i, loc)); continue;
                    case '\"': stream.Add(ScanString('\"', input, ref i, loc)); continue;

                    default: throw new ApplicationException("Uknown character " + c);
                }
            }

            // TODO: Only accurate to line level
            stream.Add(new Token(TokenType.Eof, new SourceLocation(filename, input, line, 0, storeIndex)));

            return stream;
        }

        static public Token ScanIdentifier(string input, ref int index, SourceLocation loc)
        {
            string output = "";

            for (; index < input.Length; index++)
            {
                char c = input[index];
                if (!Char.IsLetterOrDigit(c) && c != '_') break;
                else output += c;
            }

            if (index < input.Length) index--;

            if (!string.IsNullOrEmpty(output))
            {
                loc.End = index+1;
                return new Token(TokenType.Identifier, loc);
            }
            else
                return null;
        }

        static public Token ScanString(char delim, string input, ref int index, SourceLocation loc)
        {
            string output = "";

            if (index < input.Length && input[index] == delim)
            {
                for (index++; index < input.Length; index++)
                {
                    char c = input[index];
                    if (c == delim) break;
                    else output += c;
                }

                loc.End = index+1;
                return new Token(TokenType.String, loc);
            }

            return null;
        }

        static public bool ScanComment(string input, ref int index, ref int line)
        {
            index++;
            if (index >= input.Length) return false;
            char c = input[index];

            bool linecomment = c == '/';
            bool blockcomment = c == '*';

            int delimIndex = 0;
            string delim = "";
            if(linecomment) delim = "\n";
            else if(blockcomment) delim = "*/";

            for (index++; index < input.Length; index++)
            {
                c = input[index];

                if (c == '\n') line++;

                if (c == delim[delimIndex]) delimIndex++;

                if (delimIndex >= delim.Length) return true;
            }

            return false;
        }

        static public Token ScanDigit(string input, ref int index, SourceLocation loc)
        {
            string output = "";

            bool dot = false;
            bool fsym = false;

            for (; index < input.Length; index++)
            {
                char c = input[index];

                if (char.IsDigit(c))
                {
                    output += c;
                    continue;
                }

                if (c == '.' && !dot) dot = true;
                else if (c == 'f' && !fsym) fsym = true;
                else break;

                output += c;
            }

            if (index < input.Length) index--;

            if (!string.IsNullOrEmpty(output))
            {
                loc.End = index + 1;

                if (fsym)
                    return new Token(TokenType.Float, loc);
                else if (dot && !fsym)
                    return new Token(TokenType.Double, loc);
                else
                    return new Token(TokenType.Integer, loc);
            }
            else
                return null;
        }

        public Token ScanKeywords(Token token)
        {
            if (keywords.ContainsKey(token.Data))
            {
                token.Type = TokenType.Keyword;
                token.Keyword = keywords[token.Data];
            }

            return token;
        }
    }
}
